#include "gwbase/curl_wrapper.h"

#include <gwbase/base/mutex.h>
#include <gwbase/base/logging.h>

namespace dicom {

void CurlSetup() {
  static Mutex m;
  static bool inited = false;

  LockGuard l(m);
  if (inited) return;

  ::curl_global_init(CURL_GLOBAL_ALL);
  inited = true;
}

void CurlCleanup() { ::curl_global_cleanup(); }

void CurlSList::AddString(const std::string& s) {
  list_ = ::curl_slist_append(list_, s.c_str());
}

void CurlSList::AddString(const char* s) {
  list_ = ::curl_slist_append(list_, s);
}

CurlHeaders::~CurlHeaders() {
  if (headers != NULL) curl_slist_free_all(headers);
}

void CurlHeaders::AddHeader(const char* name, uint64_t val) {
  AddHeader(name, Fmt("%ld", val).c_str());
}

void CurlHeaders::AddHeader(const char* name, long val) {
  AddHeader(name, Fmt("%d", val).c_str());
}

void CurlHeaders::AddHeader(const char* name, const std::string& val) {
  std::string header = name;
  header += ": ";
  header += val;
  headers = curl_slist_append(headers, header.c_str());
}

void CurlHeaders::AddOAuthHeader(const std::string& token) {
  this->AddHeader("Authorization", StringPrintf("Bearer %s", token.c_str()));
}

void CurlForm::AddField(const char* name, const std::string& value) {
  CURLFORMcode ret = ::curl_formadd(&first_, &last_, CURLFORM_COPYNAME, name,
                                    CURLFORM_COPYCONTENTS, value.c_str(),
                                    CURLFORM_CONTENTSLENGTH, value.size());
  assert(ret == CURL_FORMADD_OK);
}

void CurlForm::AddFile(const char* name, const std::string& file_name,
                       CurlReadFileCallback cb, void* data, int64_t size) {
  assert(this->file_cb_ == NULL);
  this->file_cb_ = cb;
  this->file_size_ = size;
  this->file_cb_data_ = data;
  CURLFORMcode ret = ::curl_formadd(
      &first_, &last_, CURLFORM_COPYNAME, name, CURLFORM_FILENAME,
      file_name.c_str(), CURLFORM_STREAM, this, CURLFORM_CONTENTSLENGTH, size);
  assert(ret == CURL_FORMADD_OK);
}

void CurlForm::AddToEasyCurl(CURL* handle) {
  CURLcode ret = ::curl_easy_setopt(handle, CURLOPT_HTTPPOST, first_);
  assert(ret == CURLE_OK);

  if (file_cb_ != NULL) {
    ::curl_easy_setopt(handle, CURLOPT_READFUNCTION, ReadFileCallbackWrapper);
  }
}

int CurlForm::ReadFileCallbackWrapper(void* buf, size_t b, size_t n,
                                      void* data) {
  CurlForm* form = static_cast<CurlForm*>(data);
  assert(form != NULL);

  return form->file_cb_(buf, b, n, form->file_cb_data_);
}

void EasyCurl::AddHeaders(CurlHeaders* headers) {
  CURLcode ret =
      curl_easy_setopt(handle_, CURLOPT_HTTPHEADER, headers->GetList());
  assert(ret == CURLE_OK);
}

void EasyCurl::SetMethod(const char* method) {
  CURLcode ret = curl_easy_setopt(handle_, CURLOPT_CUSTOMREQUEST, method);
  assert(ret == CURLE_OK);
}

void EasyCurl::SetUrl(const std::string& url) {
  CURLcode ret = ::curl_easy_setopt(handle_, CURLOPT_URL, url.c_str());
  assert(ret == CURLE_OK);
}

void EasyCurl::SetUrlEncodeForm(const std::string& data) {
  CURLcode ret = ::curl_easy_setopt(handle_, CURLOPT_POSTFIELDS, data.c_str());
  assert(ret == CURLE_OK);
}

void EasyCurl::SetMultipartForm(CurlForm* form) {
  form->AddToEasyCurl(handle_);
}

void EasyCurl::SetPutFileData(CurlReadFileCallback cb, void* data,
                              size_t size) {
  CURLcode ret = ::curl_easy_setopt(handle_, CURLOPT_READFUNCTION, cb);
  assert(ret == CURLE_OK);
  ret = ::curl_easy_setopt(handle_, CURLOPT_READDATA, data);
  assert(ret == CURLE_OK);
  ret = ::curl_easy_setopt(handle_, CURLOPT_UPLOAD, 1U);
  assert(ret == CURLE_OK);
  ret = ::curl_easy_setopt(handle_, CURLOPT_CUSTOMREQUEST, "PUT");
  assert(ret == CURLE_OK);
  ret = ::curl_easy_setopt(handle_, CURLOPT_INFILESIZE_LARGE, size);
  assert(ret == CURLE_OK);
}

void EasyCurl::SetWriteFunction(CurlWriteFileCallback cb, void* data) {
  CURLcode ret = ::curl_easy_setopt(handle_, CURLOPT_WRITEFUNCTION, cb);
  assert(ret == CURLE_OK);
  ret = ::curl_easy_setopt(handle_, CURLOPT_WRITEDATA, data);
  assert(ret == CURLE_OK);
}

int EasyCurl::WriteToStringCallback(void* buf, size_t b, size_t n,
                                    void* stream) {
  assert(stream != NULL);
  std::string* str = static_cast<std::string*>(stream);
  str->append(static_cast<const char*>(buf), b * n);
  return static_cast<int>(b * n);
}

bool EasyCurl::PerformText(std::string* response) {
  response->clear();
  CURLcode ret = ::curl_easy_setopt(handle_, CURLOPT_WRITEFUNCTION,
                                    &EasyCurl::WriteToStringCallback);
  assert(ret == CURLE_OK);
  ret = ::curl_easy_setopt(handle_, CURLOPT_WRITEDATA, response);
  assert(ret == CURLE_OK);

  ret = curl_easy_perform(handle_);
  return CollectResult(ret);
}

bool EasyCurl::Perform() {
  CURLcode ret = curl_easy_perform(handle_);
  return CollectResult(ret);
}

bool EasyCurl::CollectResult(CURLcode code) {
  if (code != CURLE_OK) {
    http_status_ = 0;
    err_msg_ = ::curl_easy_strerror(code);
    LOG_INFO << "CURL Error: " << err_msg_;
    return false;
  }

  err_msg_.clear();
  ::curl_easy_getinfo(handle_, CURLINFO_RESPONSE_CODE, &http_status_);
  return true;
}

EasyCurl::EasyCurl() : handle_(NULL) {
  CurlSetup();
  handle_ = ::curl_easy_init();
  ::curl_easy_setopt(handle_, CURLOPT_NOSIGNAL, 1L);
  assert(handle_ != NULL);
}

EasyCurl::~EasyCurl() { ::curl_easy_cleanup(handle_); }

void EasyCurl::Reset() { ::curl_easy_reset(handle_); }

void EasyCurl::EnableDebugOutput(bool enabled) {
  ::curl_easy_setopt(handle_, CURLOPT_VERBOSE, (enabled ? 1U : 0U));
}
};
